/**
 * 
 */
package org.swing.utility.common.hex;

/**
 * @author lqnhu
 *
 */
public class HexUtil {
	public static char[] HEX_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7',
			'8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

	/**
	 * Creates a String from a byte array with each byte in a "Big Endian"
	 * hexidecimal format. For example, a byte 0x34 will return a String "34". A
	 * byte array of { 0x34, 035 } would return "3435".
	 * 
	 * @param buffer
	 *            The StringBuilder the byte array in hexidecimal format will be
	 *            appended to. If the buffer is null, this method will throw a
	 *            NullPointerException.
	 * @param bytes
	 *            The byte array that will be converted to a hexidecimal String.
	 *            If the byte array is null, this method will append nothing (a
	 *            noop)
	 */
	public static String toHexString(byte[] bytes) {
		if (bytes == null) {
			return "";
		}
		return toHexString(bytes, 0, bytes.length);
	}

	/**
	 * Creates a String from a byte array with each byte in a "Big Endian"
	 * hexidecimal format. For example, a byte 0x34 will return a String "34". A
	 * byte array of { 0x34, 035 } would return "3435".
	 * 
	 * @param buffer
	 *            The StringBuilder the byte array in hexidecimal format will be
	 *            appended to. If the buffer is null, this method will throw a
	 *            NullPointerException.
	 * @param bytes
	 *            The byte array that will be converted to a hexidecimal String.
	 *            If the byte array is null, this method will append nothing (a
	 *            noop)
	 * @param offset
	 *            The offset in the byte array to start from. If the offset or
	 *            length combination is invalid, this method will throw an
	 *            IllegalArgumentException.
	 * @param length
	 *            The length (from the offset) to conver the bytes. If the
	 *            offset or length combination is invalid, this method will
	 *            throw an IllegalArgumentException.
	 */
	public static String toHexString(byte[] bytes, int offset, int length) {
		if (bytes == null) {
			return "";
		}
		assertOffsetLengthValid(offset, length, bytes.length);
		// each byte is 2 chars in string
		StringBuilder buffer = new StringBuilder(length * 2);
		appendHexString(buffer, bytes, offset, length);
		return buffer.toString();
	}

	/**
	 * Appends a byte array to a StringBuilder with each byte in a "Big Endian"
	 * hexidecimal format. For example, a byte 0x34 will be appended as a String
	 * in format "34". A byte array of { 0x34, 035 } would append "3435".
	 * 
	 * @param buffer
	 *            The StringBuilder the byte array in hexidecimal format will be
	 *            appended to. If the buffer is null, this method will throw a
	 *            NullPointerException.
	 * @param bytes
	 *            The byte array that will be converted to a hexidecimal String.
	 *            If the byte array is null, this method will append nothing (a
	 *            noop)
	 */
	public static void appendHexString(StringBuilder buffer, byte[] bytes) {
		assertNotNull(buffer);
		if (bytes == null) {
			return; // do nothing (a noop)
		}
		appendHexString(buffer, bytes, 0, bytes.length);
	}

	/**
	 * Appends a byte array to a StringBuilder with each byte in a "Big Endian"
	 * hexidecimal format. For example, a byte 0x34 will be appended as a String
	 * in format "34". A byte array of { 0x34, 035 } would append "3435".
	 * 
	 * @param buffer
	 *            The StringBuilder the byte array in hexidecimal format will be
	 *            appended to. If the buffer is null, this method will throw a
	 *            NullPointerException.
	 * @param bytes
	 *            The byte array that will be converted to a hexidecimal String.
	 *            If the byte array is null, this method will append nothing (a
	 *            noop)
	 * @param offset
	 *            The offset in the byte array to start from. If the offset or
	 *            length combination is invalid, this method will throw an
	 *            IllegalArgumentException.
	 * @param length
	 *            The length (from the offset) to conver the bytes. If the
	 *            offset or length combination is invalid, this method will
	 *            throw an IllegalArgumentException.
	 */
	static public void appendHexString(StringBuilder buffer, byte[] bytes,
			int offset, int length) {
		assertNotNull(buffer);
		if (bytes == null) {
			return; // do nothing (a noop)
		}
		assertOffsetLengthValid(offset, length, bytes.length);
		int end = offset + length;
		for (int i = offset; i < end; i++) {
			int nibble1 = (bytes[i] & 0xF0) >>> 4;
			int nibble0 = (bytes[i] & 0x0F);
			buffer.append(HEX_TABLE[nibble1]);
			buffer.append(HEX_TABLE[nibble0]);
		}
	}

	/**
	 * Creates a 2 character hex String from a byte with the byte in a
	 * "Big Endian" hexidecimal format. For example, a byte 0x34 will be
	 * returned as a String in format "34". A byte of value 0 will be returned
	 * as "00".
	 * 
	 * @param value
	 *            The byte value that will be converted to a hexidecimal String.
	 */
	static public String toHexString(byte value) {
		StringBuilder buffer = new StringBuilder(2);
		appendHexString(buffer, value);
		return buffer.toString();
	}

	/**
	 * Appends 2 characters to a StringBuilder with the byte in a "Big Endian"
	 * hexidecimal format. For example, a byte 0x34 will be appended as a String
	 * in format "34". A byte of value 0 will be appended as "00".
	 * 
	 * @param buffer
	 *            The StringBuilder the byte value in hexidecimal format will be
	 *            appended to. If the buffer is null, this method will throw a
	 *            NullPointerException.
	 * @param value
	 *            The byte value that will be converted to a hexidecimal String.
	 */
	static public void appendHexString(StringBuilder buffer, byte value) {
		assertNotNull(buffer);
		int nibble = (value & 0xF0) >>> 4;
		buffer.append(HEX_TABLE[nibble]);
		nibble = (value & 0x0F);
		buffer.append(HEX_TABLE[nibble]);
	}

	/**
	 * Creates a 4 character hex String from a short with the short in a
	 * "Big Endian" hexidecimal format. For example, a short 0x1234 will be
	 * returned as a String in format "1234". A short of value 0 will be
	 * returned as "0000".
	 * 
	 * @param value
	 *            The short value that will be converted to a hexidecimal
	 *            String.
	 */
	static public String toHexString(short value) {
		StringBuilder buffer = new StringBuilder(4);
		appendHexString(buffer, value);
		return buffer.toString();
	}

	/**
	 * Appends 4 characters to a StringBuilder with the short in a "Big Endian"
	 * hexidecimal format. For example, a short 0x1234 will be appended as a
	 * String in format "1234". A short of value 0 will be appended as "0000".
	 * 
	 * @param buffer
	 *            The StringBuilder the short value in hexidecimal format will
	 *            be appended to. If the buffer is null, this method will throw
	 *            a NullPointerException.
	 * @param value
	 *            The short value that will be converted to a hexidecimal
	 *            String.
	 */
	static public void appendHexString(StringBuilder buffer, short value) {
		assertNotNull(buffer);
		int nibble = (value & 0xF000) >>> 12;
		buffer.append(HEX_TABLE[nibble]);
		nibble = (value & 0x0F00) >>> 8;
		buffer.append(HEX_TABLE[nibble]);
		nibble = (value & 0x00F0) >>> 4;
		buffer.append(HEX_TABLE[nibble]);
		nibble = (value & 0x000F);
		buffer.append(HEX_TABLE[nibble]);
	}

	/**
	 * Creates an 8 character hex String from an int twith the int in a
	 * "Big Endian" hexidecimal format. For example, an int 0xFFAA1234 will be
	 * returned as a String in format "FFAA1234". A int of value 0 will be
	 * returned as "00000000".
	 * 
	 * @param value
	 *            The int value that will be converted to a hexidecimal String.
	 */
	static public String toHexString(int value) {
		StringBuilder buffer = new StringBuilder(8);
		appendHexString(buffer, value);
		return buffer.toString();
	}

	/**
	 * Appends 8 characters to a StringBuilder with the int in a "Big Endian"
	 * hexidecimal format. For example, a int 0xFFAA1234 will be appended as a
	 * String in format "FFAA1234". A int of value 0 will be appended as
	 * "00000000".
	 * 
	 * @param buffer
	 *            The StringBuilder the int value in hexidecimal format will be
	 *            appended to. If the buffer is null, this method will throw a
	 *            NullPointerException.
	 * @param value
	 *            The int value that will be converted to a hexidecimal String.
	 */
	static public void appendHexString(StringBuilder buffer, int value) {
		assertNotNull(buffer);
		int nibble = (value & 0xF0000000) >>> 28;
		buffer.append(HEX_TABLE[nibble]);
		nibble = (value & 0x0F000000) >>> 24;
		buffer.append(HEX_TABLE[nibble]);
		nibble = (value & 0x00F00000) >>> 20;
		buffer.append(HEX_TABLE[nibble]);
		nibble = (value & 0x000F0000) >>> 16;
		buffer.append(HEX_TABLE[nibble]);
		nibble = (value & 0x0000F000) >>> 12;
		buffer.append(HEX_TABLE[nibble]);
		nibble = (value & 0x00000F00) >>> 8;
		buffer.append(HEX_TABLE[nibble]);
		nibble = (value & 0x000000F0) >>> 4;
		buffer.append(HEX_TABLE[nibble]);
		nibble = (value & 0x0000000F);
		buffer.append(HEX_TABLE[nibble]);
	}

	/**
	 * Creates a 16 character hex String from a long with the long in a
	 * "Big Endian" hexidecimal format. For example, a long 0xAABBCCDDEE123456
	 * will be returned as a String in format "AABBCCDDEE123456". A long of
	 * value 0 will be returned as "0000000000000000".
	 * 
	 * @param value
	 *            The long value that will be converted to a hexidecimal String.
	 */
	static public String toHexString(long value) {
		StringBuilder buffer = new StringBuilder(16);
		appendHexString(buffer, value);
		return buffer.toString();
	}

	/**
	 * Appends 16 characters to a StringBuilder with the long in a "Big Endian"
	 * hexidecimal format. For example, a long 0xAABBCCDDEE123456 will be
	 * appended as a String in format "AABBCCDDEE123456". A long of value 0 will
	 * be appended as "0000000000000000".
	 * 
	 * @param buffer
	 *            The StringBuilder the long value in hexidecimal format will be
	 *            appended to. If the buffer is null, this method will throw a
	 *            NullPointerException.
	 * @param value
	 *            The long value that will be converted to a hexidecimal String.
	 */
	static public void appendHexString(StringBuilder buffer, long value) {
		appendHexString(buffer, (int) ((value & 0xFFFFFFFF00000000L) >>> 32));
		appendHexString(buffer, (int) (value & 0x00000000FFFFFFFFL));
	}

	static private void assertNotNull(StringBuilder buffer) {
		if (buffer == null) {
			throw new NullPointerException("The buffer cannot be null");
		}
	}

	static private void assertOffsetLengthValid(int offset, int length,
			int arrayLength) {
		if (offset < 0) {
			throw new IllegalArgumentException("The array offset was negative");
		}
		if (length < 0) {
			throw new IllegalArgumentException("The array length was negative");
		}
		if (offset + length > arrayLength) {
			throw new ArrayIndexOutOfBoundsException(
					"The array offset+length would access past end of array");
		}
	}

	/**
	 * Converts a hexidecimal character such as '0' or 'A' or 'a' to its integer
	 * value such as 0 or 10. Used to decode hexidecimal Strings to integer
	 * values.
	 * 
	 * @param c
	 *            The hexidecimal character
	 * @return The integer value the character represents
	 * @throws IllegalArgumentException
	 *             Thrown if a character that does not represent a hexidecimal
	 *             character is used.
	 */
	static public int hexCharToIntValue(char c) {
		if (c == '0') {
			return 0;
		} else if (c == '1') {
			return 1;
		} else if (c == '2') {
			return 2;
		} else if (c == '3') {
			return 3;
		} else if (c == '4') {
			return 4;
		} else if (c == '5') {
			return 5;
		} else if (c == '6') {
			return 6;
		} else if (c == '7') {
			return 7;
		} else if (c == '8') {
			return 8;
		} else if (c == '9') {
			return 9;
		} else if (c == 'A' || c == 'a') {
			return 10;
		} else if (c == 'B' || c == 'b') {
			return 11;
		} else if (c == 'C' || c == 'c') {
			return 12;
		} else if (c == 'D' || c == 'd') {
			return 13;
		} else if (c == 'E' || c == 'e') {
			return 14;
		} else if (c == 'F' || c == 'f') {
			return 15;
		} else {
			throw new IllegalArgumentException("The character [" + c
					+ "] does not represent a valid hex digit");
		}
	}

	/**
	 * Creates a byte array from a CharSequence (String, StringBuilder, etc.)
	 * containing only valid hexidecimal formatted characters. Each grouping of
	 * 2 characters represent a byte in "Big Endian" format. The hex
	 * CharSequence must be an even length of characters. For example, a String
	 * of "1234" would return the byte array { 0x12, 0x34 }.
	 * 
	 * @param hexString
	 *            The String, StringBuilder, etc. that contains the sequence of
	 *            hexidecimal character values.
	 * @return A new byte array representing the sequence of bytes created from
	 *         the sequence of hexidecimal characters. If the hexString is null,
	 *         then this method will return null.
	 */
	public static byte[] toByteArray(CharSequence hexString) {
		if (hexString == null) {
			return null;
		}
		return toByteArray(hexString, 0, hexString.length());
	}

	/**
	 * Creates a byte array from a CharSequence (String, StringBuilder, etc.)
	 * containing only valid hexidecimal formatted characters. Each grouping of
	 * 2 characters represent a byte in "Big Endian" format. The hex
	 * CharSequence must be an even length of characters. For example, a String
	 * of "1234" would return the byte array { 0x12, 0x34 }.
	 * 
	 * @param hexString
	 *            The String, StringBuilder, etc. that contains the sequence of
	 *            hexidecimal character values.
	 * @param offset
	 *            The offset within the sequence to start from. If the offset is
	 *            invalid, will cause an IllegalArgumentException.
	 * @param length
	 *            The length from the offset to convert. If the length is
	 *            invalid, will cause an IllegalArgumentException.
	 * @return A new byte array representing the sequence of bytes created from
	 *         the sequence of hexidecimal characters. If the hexString is null,
	 *         then this method will return null.
	 */
	public static byte[] toByteArray(CharSequence hexString, int offset,
			int length) {
		if (hexString == null) {
			return null;
		}
		assertOffsetLengthValid(offset, length, hexString.length());
		// a hex string must be in increments of 2
		if ((length % 2) != 0) {
			throw new IllegalArgumentException(
					"The hex string did not contain an even number of characters [actual="
							+ length + "]");
		}
		// convert hex string to byte array
		byte[] bytes = new byte[length / 2];
		int j = 0;
		int end = offset + length;
		for (int i = offset; i < end; i += 2) {
			int highNibble = hexCharToIntValue(hexString.charAt(i));
			int lowNibble = hexCharToIntValue(hexString.charAt(i + 1));
			bytes[j++] = (byte) (((highNibble << 4) & 0xF0) | (lowNibble & 0x0F));
		}
		return bytes;
	}
}
